﻿// 3647. 惠民工程.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <algorithm>


using namespace std;
/*
https://www.acwing.com/problem/content/3650/

市政府“惠民工程”的目标是在全市 n个居民点间之架设煤气管道
（但不一定有直接的管道相连，只要能间接通过管道可达即可）。

很显然最多可架设 n(n−1)/2条管道，然而实际上要连通 n个居民点只需架设 n−1条管道就可以了。

现请你编写程序，计算出该惠民工程需要的最低成本。

输入格式
输入包含多组测试数据。

每组数据第一行包含两个整数 n和 m，表示居民点数量和评估管道数量。

接下来 m行，每行包含三个整数 a,b,c
，表示居民点 a和 b之间架设管道需要 c的成本。

居民点编号 1∼n。

输出格式
每组数据输出一行一个结果，表示全市管道畅通所需要的最低成本。

若统计数据不足以保证畅通，则输出 ?。

数据范围
2≤n≤100
,
1≤m≤n(n−1)2
,
1≤a,b≤n
,
1≤c≤100
,
每个输入最多包含 100组数据。

输入样例：
3 3
1 2 1
1 3 2
2 3 4
3 1
2 3 2
输出样例：
3
?
*/
const int INF = 0x3f3f3f3f;
const int N = 150, M = 10010;
int n, m;
int f[N];

struct Edge {
	int a, b, w;
	bool operator <(const Edge& W) const {
		return w < W.w;
	}
}edges[M];

int find(int x)     // 并查集核心操作
{
    if (f[x] != x) f[x] = find(f[x]);
    return f[x];
}

int kruskal()
{
    sort(edges, edges + m);

    for (int i = 1; i <= n; i++) f[i] = i;    // 初始化并查集

    int res = 0, cnt = 0;
    for (int i = 0; i < m; i++)
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;

        a = find(a), b = find(b);
        if (a != b)     // 如果两个连通块不连通，则将这两个连通块合并
        {
            f[a] = b;
            res += w;
            cnt++;
        }
    }

    if (cnt < n - 1) return INF;
    return res;
}

 

void solve() {

    for (int i = 0; i < m; i++) {
        cin >> edges[i].a >> edges[i].b >> edges[i].w;
    }
    int res = kruskal();
    if (res != 0x3f3f3f3f) {
        cout << res << endl;
    }
    else {
        cout << "?" << endl;
    }
}

int main()
{
	while (cin >> n >> m) {

		solve();
	}

	return 0;
}

 